tool
class_name PolyhedronPosition

# Rotation flags for cuboid polyhedron.
# * bit 0-2: determines if the local axis is inverted
#   (x,y,z, 0=normal, 1=inverted)
# * bit 3-4,5-6,7-8: determines which local axis is mapped to
#   global x,y,z axis
# * example: ROT_ABC means local A axis <-> global X axis,
#   local B axis <-> global Y axis, local C axis <-> global Z axis
enum {
	UPPER_X = 1, # mirrored
	UPPER_Y = 2, # mirrored
	UPPER_Z = 4, # mirrored
	UPPER_XY = 1 | 2,
	UPPER_XZ = 1 | 4,
	UPPER_YZ = 2 | 4,
	UPPER_XYZ = 1 | 2 | 4, # mirrored
	UPPER_MASK = 1 | 2 | 4,
	ROT_XYZ = (0 << 3) | (1 << 5) | (2 << 7),
	ROT_YZX = (1 << 3) | (2 << 5) | (0 << 7),
	ROT_ZXY = (2 << 3) | (0 << 5) | (1 << 7),
	ROT_XZY = (0 << 3) | (2 << 5) | (1 << 7), # mirrored
	ROT_YXZ = (1 << 3) | (0 << 5) | (2 << 7), # mirrored
	ROT_ZYX = (2 << 3) | (1 << 5) | (0 << 7), # mirrored
	ROT_MASK = (3 << 3) | (3 << 5) | (3 << 7),
}

const DEFAULT = {
	"map_data_name": "",
	"position": Vector3(0, 0, 0),
	"flags": ROT_XYZ,
}

const allPossibleFlagsForCuboidSize = [
	ROT_XYZ,
	ROT_YZX,
	ROT_ZXY,
	ROT_XZY | UPPER_X,
	ROT_YXZ | UPPER_X,
	ROT_ZYX | UPPER_X,
]

const allPossibleFlagsForCuboid = [
	ROT_XYZ,
	ROT_XYZ | UPPER_XY,
	ROT_XYZ | UPPER_XZ,
	ROT_XYZ | UPPER_YZ,
	ROT_YZX,
	ROT_YZX | UPPER_XY,
	ROT_YZX | UPPER_XZ,
	ROT_YZX | UPPER_YZ,
	ROT_ZXY,
	ROT_ZXY | UPPER_XY,
	ROT_ZXY | UPPER_XZ,
	ROT_ZXY | UPPER_YZ,
	ROT_XZY | UPPER_X,
	ROT_XZY | UPPER_Y,
	ROT_XZY | UPPER_Z,
	ROT_XZY | UPPER_XYZ,
	ROT_YXZ | UPPER_X,
	ROT_YXZ | UPPER_Y,
	ROT_YXZ | UPPER_Z,
	ROT_YXZ | UPPER_XYZ,
	ROT_ZYX | UPPER_X,
	ROT_ZYX | UPPER_Y,
	ROT_ZYX | UPPER_Z,
	ROT_ZYX | UPPER_XYZ,
]

# moving type
enum {
	ROLL,
	MOVE,
	ROTATE
}

# moving direction (in global coordinate)
enum {
	HORZ_NEG,
	HORZ_POS,
	VERT_NEG,
	VERT_POS,
	Y_NEG, # this is also Y_CCW
	Y_POS, # this is also Y_CW
}

static func get_basis(flags: int) -> Basis:
	var ret: Basis = Basis(Vector3.ZERO, Vector3.ZERO, Vector3.ZERO)

	for i in range(3):
		var idx: int = (flags >> (3 + i * 2)) & 3
		ret[idx][i] = -1 if ((flags >> idx) & 1) else 1

	return ret

# this is x + y + z of get_basis()
static func get_sum_of_basis(flags: int) -> Vector3:
	var ret: Vector3 = Vector3.ZERO

	for i in range(3):
		var idx: int = (flags >> (3 + i * 2)) & 3
		ret[i] = -1 if ((flags >> idx) & 1) else 1

	return ret

static func map_local_to_global(pp: Dictionary, v: Vector3) -> Vector3:
	var flags: int = pp.get("flags", ROT_XYZ)
	var ret: Vector3 = Vector3.ZERO

	for i in range(3):
		var idx: int = (flags >> (3 + i * 2)) & 3
		ret[i] = -1 - v[idx] if ((flags >> idx) & 1) else v[idx]

	return ret + pp.get("position", Vector3(0, 0, 0))

static func map_global_to_local(pp: Dictionary, v: Vector3) -> Vector3:
	v -= pp.get("position", Vector3(0, 0, 0))
	var flags: int = pp.get("flags", ROT_XYZ)
	var ret: Vector3 = Vector3.ZERO

	for i in range(3):
		var idx: int = (flags >> (3 + i * 2)) & 3
		ret[idx] = -1 - v[i] if ((flags >> idx) & 1) else v[i]

	return ret

static func move(pp: Dictionary, param: Dictionary, anim = null) -> Dictionary:
	var pos: Vector3 = pp.get("position", Vector3(0, 0, 0))
	var flags: int = pp.get("flags", ROT_XYZ)

	match param:
		{"type": ROLL, "lbound": var _lb, "ubound": var _ub, "dir": var _d, ..}:
			var lbound: Vector3 = _lb as Vector3
			var ubound: Vector3 = _ub as Vector3
			var dir: int = _d as int

			# get current size
			var cur_lbound: Vector3 = Vector3()
			var cur_ubound: Vector3 = Vector3()
			for i in range(3):
				var idx: int = (flags >> (3 + i * 2)) & 3
				if (flags >> idx) & 1:
					cur_lbound[i] = -1 - ubound[idx]
					cur_ubound[i] = -1 - lbound[idx]
				else:
					cur_lbound[i] = lbound[idx]
					cur_ubound[i] = ubound[idx]

			match dir:
				HORZ_NEG:
					if anim != null:
						anim["rot_center"] = Vector3(pos.x, \
							pos.y + cur_lbound.y, pos.z + cur_lbound.z)
						anim["rot_axis"] = Vector3(-1, 0, 0)
					pos.y += cur_lbound.y - cur_lbound.z
					pos.z += cur_lbound.y + cur_lbound.z
					flags = rotate_flags(flags, Vector3(-1, 0, 0))
				HORZ_POS:
					if anim != null:
						anim["rot_center"] = Vector3(pos.x, \
							pos.y + cur_lbound.y, pos.z + cur_ubound.z + 1)
						anim["rot_axis"] = Vector3(1, 0, 0)
					pos.y += cur_lbound.y + cur_ubound.z + 1
					pos.z += cur_ubound.z + 1 - cur_lbound.y
					flags = rotate_flags(flags, Vector3(1, 0, 0))
				VERT_NEG:
					if anim != null:
						anim["rot_center"] = Vector3(pos.x + cur_lbound.x, \
							pos.y + cur_lbound.y, pos.z)
						anim["rot_axis"] = Vector3(0, 0, 1)
					pos.y += cur_lbound.y - cur_lbound.x
					pos.x += cur_lbound.y + cur_lbound.x
					flags = rotate_flags(flags, Vector3(0, 0, 1))
				VERT_POS:
					if anim != null:
						anim["rot_center"] = Vector3(pos.x + cur_ubound.x + 1, \
							pos.y + cur_lbound.y, pos.z)
						anim["rot_axis"] = Vector3(0, 0, -1)
					pos.y += cur_lbound.y + cur_ubound.x + 1
					pos.x += cur_ubound.x + 1 - cur_lbound.y
					flags = rotate_flags(flags, Vector3(0, 0, -1))

		{"type": MOVE, "dir": var _d, ..}:
			var dir: int = _d as int
			match dir:
				HORZ_NEG:
					if anim != null:
						anim["move_axis"] = Vector3(0, 0, -1)
					pos.z -= 1
				HORZ_POS:
					if anim != null:
						anim["move_axis"] = Vector3(0, 0, 1)
					pos.z += 1
				VERT_NEG:
					if anim != null:
						anim["move_axis"] = Vector3(-1, 0, 0)
					pos.x -= 1
				VERT_POS:
					if anim != null:
						anim["move_axis"] = Vector3(1, 0, 0)
					pos.x += 1
				Y_NEG:
					if anim != null:
						anim["move_axis"] = Vector3(0, -1, 0)
					pos.y -= 1
				Y_POS:
					if anim != null:
						anim["move_axis"] = Vector3(0, 1, 0)
					pos.y += 1

		{"type": ROTATE, "dir": var _d, ..}:
			var dir: int = _d as int

			var rot_axis: Vector3 = Vector3.ZERO

			match dir:
				HORZ_NEG:
					rot_axis = Vector3(-1, 0, 0)
				HORZ_POS:
					rot_axis = Vector3(1, 0, 0)
				VERT_NEG:
					rot_axis = Vector3(0, 0, 1)
				VERT_POS:
					rot_axis = Vector3(0, 0, -1)
				Y_NEG:
					rot_axis = Vector3(0, -1, 0)
				Y_POS:
					rot_axis = Vector3(0, 1, 0)

			if rot_axis.length_squared() > 0.25:
				var old_sum: Vector3 = get_sum_of_basis(flags)

				if anim != null:
					anim["rot_center"] = pos + old_sum * 0.5
					anim["rot_axis"] = rot_axis

				flags = rotate_flags(flags, rot_axis)
				var new_sum: Vector3 = get_sum_of_basis(flags)
				pos += (old_sum - new_sum) * 0.5

	return {
		"map_data_name": pp.get("map_data_name", ""),
		"position": pos,
		"flags": flags,
	}

static func rotate_flags(flags: int, axis: Vector3) -> int:
	var oflags: PoolIntArray = PoolIntArray()

	for i in range(3):
		var idx: int = (flags >> (3 + i * 2)) & 3
		oflags.push_back(idx)

	if abs(axis.x) > 0.5:
		# rotate around x axis
		flags = (flags & 7) | (oflags[0] << 3) | (oflags[2] << 5) | (oflags[1] << 7)
		return flags ^ (1 << oflags[2 if axis.x > 0 else 1])
	elif abs(axis.y) > 0.5:
		# rotate around y axis
		flags = (flags & 7) | (oflags[2] << 3) | (oflags[1] << 5) | (oflags[0] << 7)
		return flags ^ (1 << oflags[0 if axis.y > 0 else 2])
	elif abs(axis.z) > 0.5:
		# rotate around z axis
		flags = (flags & 7) | (oflags[1] << 3) | (oflags[0] << 5) | (oflags[2] << 7)
		return flags ^ (1 << oflags[1 if axis.z > 0 else 0])
	else:
		return flags
